package flexo.graphic;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GLContext;

public class Geometry extends flexo.common.ReferenceCount
{
	/**
	 * This paramaters refer to how much you update the
	 * vertices and indices of this geometry. Then it will
	 * optimize it's use depending on that.
	 * 
	 * Available values are:
	 *  + UPDATE_NEVER: You'll create the geometry just once, and never change it (background objects).
	 *  + UPDATE_SOME: Sometimes the geometry will change (text).
	 *  + UPDATE_ALWAYS: The geometry is updated per-frame (particles).
	 */
	public static final int UPDATE_NEVER = ARBVertexBufferObject.GL_STATIC_DRAW_ARB;
	public static final int UPDATE_SOME = ARBVertexBufferObject.GL_DYNAMIC_DRAW_ARB;
	public static final int UPDATE_ALWAYS = ARBVertexBufferObject.GL_STREAM_DRAW_ARB;
	
	// 3 for vertex, 3 for normal, 4 for color and 2 for texture coordinates. * 4 for bytes
	public static final int VERTEX_SIZE = 3 + 3 + 4 + 2;
	public static final int STRIDE = VERTEX_SIZE * 4;
		
	protected int vboVertexId;
	protected int vboIndexId;
	protected int numIndices;
	protected int updateType;
		
	
	public Geometry(FloatBuffer vertices, IntBuffer indices, int numIndices)
	{
		create(vertices, indices, numIndices, UPDATE_NEVER);
	}
	
	public Geometry(FloatBuffer vertices, IntBuffer indices, int numIndices, int updateType)
	{
		create(vertices, indices, numIndices, updateType);
	}
	
	/**
	 * We limit the texture creation to inside the package.
	 * Here we create a Vertex Buffer Object (OpenGL) to hold the geometry.
	 */
	protected void create(FloatBuffer vertices, IntBuffer indices, int numIndices, int updateType)
	{
		this.numIndices = numIndices;
		
		vboVertexId = createVBOID();
		vboIndexId = createVBOID();
		
		this.updateType = updateType;
		
		setVertices(vertices);
		setIndices(indices);	
	}
	
	public void destroy()
	{
		deleteVBO(vboVertexId);
		deleteVBO(vboIndexId);
		flexo.graphic.GraphicSystem gs = (flexo.graphic.GraphicSystem)flexo.Kernel.getSystem(flexo.graphic.GraphicSystem.ID);
		gs.geometryPool.remove(this);
	}
	
	private static void deleteVBO(int id)
	{
		IntBuffer buffer = BufferUtils.createIntBuffer(1);
		buffer.put(id);
		ARBVertexBufferObject.glDeleteBuffersARB(buffer);
	}
		
	private static int createVBOID()
	{
		if (GLContext.getCapabilities().GL_ARB_vertex_buffer_object)
		{
			IntBuffer buffer = BufferUtils.createIntBuffer(1);
			ARBVertexBufferObject.glGenBuffersARB(buffer);
			return buffer.get(0);
		}
			return 0;
	}
	
	public void setVertices(FloatBuffer buffer)
	{		
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, this.vboVertexId);
		ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, buffer, this.updateType);
	}
	
	public void setIndices(IntBuffer buffer)
	{
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, this.vboIndexId);
		ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, buffer, this.updateType);		
	}
	
	public void render()
	{
		// Set up the VBO structure		
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboVertexId);				
		
		// 0 as its the first in the chunk, i.e. no offset. * 4 to convert to bytes.
		GL11.glVertexPointer(3, GL11.GL_FLOAT, Geometry.STRIDE, 0);
		 
		// normals
		// 3 components is the initial offset from 0, then convert to bytes (3*4)
		GL11.glNormalPointer(GL11.GL_FLOAT, Geometry.STRIDE, 12);
		 
		// colours
		// (6*4) is the number of byte to skip to get to the colour chunk (3+3)*4
		GL11.glColorPointer(4, GL11.GL_FLOAT, Geometry.STRIDE, 24);
		 
		// texture coordinates
		GL11.glTexCoordPointer(2, GL11.GL_FLOAT, Geometry.STRIDE, 40);
		
		GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
		GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
		GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);				
		
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, vboIndexId);
		GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, numIndices-1, numIndices, GL11.GL_UNSIGNED_INT, 0);
		
		GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
		GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
		GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, 0);
		ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
	}
}
